package org.exussum0.eyecks1;

import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.TokenRewriteStream;
import org.antlr.runtime.tree.CommonTree;
import org.exussum0.eyecks1.antlr.Java1_5Lexer;
import org.exussum0.eyecks1.antlr.Java1_5Parser;

/**
 * Within a Java file, return all imports, the exposed public class, the package
 * and all other possible classes used within it.
 */
public class ClassFinder {

	private static final int PACKAGE_DECLARATION = Java1_5Parser.T__25;
	private static final int TOP_LEVEL_CLASS_DECLARATION = Java1_5Parser.T__37;
	private static final int IMPORT_STATEMENT = Java1_5Parser.T__27;
	private static final int PACKAGE_DELIMITER = Java1_5Parser.T__29;
	private static final int CLASS_OR_METHOD = Java1_5Parser.Identifier;

	/**
	 * Visitor that is run per class found using the find method.
	 * 
	 * @author sportee
	 * 
	 */
	public static interface Visitor {
		public void visit(String clazz, String pkg, Collection<String> imports,
				String dstClass) throws Exception;
	}

	/**
	 * Find will return all suspected classes within a Java file.
	 * 
	 * -- BUG: The parser is not completely trustable and will return other
	 * types. It is up to the user to verify that all entities are classes and
	 * that each Java file has no method names that are also classes. i.e.
	 * 
	 * public Integer Integer( ... )
	 * 
	 * will confuse the current working version. More work with AST traversal is
	 * necessary. -- BUG: Multiple class declarations per file not yet tested
	 * 
	 * 
	 * @param file
	 *            Java file on the file system.
	 * @param visitor
	 *            Mixin/Visitor code to execute per file.
	 * @throws Exception
	 */
	public void find(File file, Visitor visitor) throws Exception {
		ANTLRFileStream fs = new ANTLRFileStream(file.toString());
		Java1_5Lexer lex = new Java1_5Lexer(fs);
		TokenRewriteStream tokens = new TokenRewriteStream(lex);
		Java1_5Parser grammar = new Java1_5Parser(tokens);
		CommonTree tree = (CommonTree) grammar.compilationUnit().getTree();

		findClassesAndPackage(tree, visitor);
	}

	/**
	 * Given an AST in the form of a list, traverse all nodes and run the
	 * visitor across all nodes. This is heavily dependent on the grammar
	 * provided by Terence Parr, et al.
	 * 
	 * @param tree Antlr AST representing a Java file.
	 * @param visitor Visitor to execute on all classes found.
	 * @throws Exception
	 */
	private void findClassesAndPackage(CommonTree tree, Visitor visitor)
			throws Exception {
		StringBuffer pkg = new StringBuffer();

		Set<String> imports = new HashSet<String>();
		StringBuffer usedClass = new StringBuffer();
		StringBuffer imp = new StringBuffer();

		String fullClass = "";
		int childCount = tree.getChildCount();
		for (int x = 0; x < childCount; x++) {
			CommonTree node = (CommonTree) tree.getChild(x);
			switch (node.getType()) {

			case PACKAGE_DECLARATION:
				x++;
				for (; x < childCount
						&& tree.getChild(x).getType() != Java1_5Parser.T__26; x++)
					pkg.append(tree.getChild(x).getText());
				break;
			case IMPORT_STATEMENT:
				x++;
				imp.setLength(0);
				for (; x < childCount
						&& tree.getChild(x).getType() != Java1_5Parser.T__26; x++)
					imp.append(tree.getChild(x).getText());
				imports.add(imp.toString());
				break;
			case TOP_LEVEL_CLASS_DECLARATION:
				x++;
				fullClass = pkg.toString() + "." + tree.getChild(x).getText();
				break;
			case CLASS_OR_METHOD:
				usedClass.setLength(0);
				boolean contin;
				do {
					usedClass.append(tree.getChild(x));
					x++;
					int typeCurrent = tree.getChild(x).getType();
					int previousType = tree.getChild(x - 1).getType();
					contin = (typeCurrent == CLASS_OR_METHOD && previousType == PACKAGE_DELIMITER)
							|| (typeCurrent == PACKAGE_DELIMITER && previousType == CLASS_OR_METHOD);
				} while (contin);

				visitor.visit(fullClass, pkg.toString(), imports, usedClass
						.toString());
				break;
			}
		}

	}
}
